跳到主要内容

创建和使用触发器

本章描述 CREATE TRIGGER 语句的每个组成部分的用途,说明触发器的一些用法,并描述将 SPL 例程用作触发器的优点。

此外,本章还描述可在视图上定义的 INSTEAD OF 触发器。

SQL 触发器是驻留在数据库中的一种机制。具有使用许可权的任何用户都可以使用它。SQL 触发器指定当数据操纵语言(DML)操作(INSERT 、SELECT 、DELETE 或 UPDATE 语句)时,数据库服务器应自动执行一个或多个附加操作。对于在视图上定义的触发器,视图基本表上的触发操替换触发事件。对于表或视图上的触发器,触发操作可以是 INSERT 、DELETE 、UPDATE 、EXECUTE PROCEDURE 或 EXECUTE FUNCTION 语句。

GBase 8s 还支持用 C 或 Java™ 编写的用户定义的例程作为触发操作。

有关如何撰写 C UDR 以获取有关触发器事件的元数据信息,请参阅《GBase 8s DataBlade API 程序员指南》。

何时使用触发器

因为触发器驻留在数据库中,且具有必需特权的任何用户都可以使用它,所以触发器允许您编写可供多个应用程序使用的一组 SQL 语句。它可在多个程序需要执行同一数据库操作时避免冗余码。

可使用触发器执行下列操作以及在此列表中找不到的其它操作:

  • 在数据库中创建活动的审计跟踪。例如:可通过更新审计表的确认信息来跟踪对订单的更新。
  • 实现业务规则。例如:可以确定何时订单超出客户的信用卡限制并对此情况显示一条消息。
  • 派生表内或数据内未提供的其它数据。例如:当对 items 表的 quantity 列进行更新时,可以计算对 total_price 列的相应调整。
  • 强制执行引用完整性。例如:在删除客户时,可以使用触发器来删除 orders 表中具有相同客户号的相应行。

如何创建触发器

使用 CREATE TRIGGER 语句定义新触发器。CREATE TRIGGER 语句是数据定义语句,它将称为触发操作的 SQL 语句与表上的诱发事件相关联。当发生诱发操作时,它触发存储在数据库中的关联 SQL 语句。

在本示例中,触发事件是引用 items 表中的 quantity 列的 UPDATE 语句。下图说明激活触发器的 DML 操作(称为触发器事件)与触发操作之间的关系。

图: 触发事件和触发操作

CREATE TRIGGER 语句由执行下列操作的子句组成:

  • 声明触发器名称。
  • 指定在指定表或试图上作为触发事件的 DML 操作。
  • 定义该事件触发器的 SQL 操作。

FOR EACH ROW 触发操作中讨论了称为 REFERENCING 子句的可选子句。

要创建触发器,使用 DB-Access 或某个 SQL API。本节描述当您在 DB-Access 中使用交互查询语言选项输入 CREATE TRIGGER 语句时的用法。在 SQL API 中,您在语句前加上将该语句标识为嵌入式语句的符号或关键字。

声明触发器名称

触发器名称标识触发器,且该名称在数据库的触发器名称中必须唯一。在语句中,触发名称跟在 CREATE TRIGGER 后面。与任何 SQL 标识一样,该名称最长为 128 个字节,以字母开始并由字母、数字和下划线(_)组成。在以下示例中,所示的 CREATE TRIGGER 语句部分声明了触发器的名称upqty:

CREATE TRIGGER upqty -- declare trigger name

指定触发器事件

触发器事件是一种激活触发器的 DML 语句。当对表执行此类型的语句时,数据库服务器执行组成触发操作的 SQL 语句。对于表,触发器事件可以是 INSERT 、SELECT 、DELETE 或 UPDATE 语句。对于 UPDATE 或 SELECT 触发事件,可以指定表中一列或多列可以激活触发器。可以在同一表上定义多个 INSERT 、SELECT 、DELETE 和 UPDATE 触发器,也可以在同一视图上定义多个 INSERT 、DELETE 和 UPDATE 触发器。

在当前数据库中的一个表或一个视图上,只能创建一个触发器。触发器不能引用远程表或视图。

在以下 CREATE TRIGGER 语句片段中,触发事件定义为更新 items 表中 quantity 列:

CREATE TRIGGER upqty
UPDATE OF quantity ON items -- an UPDATE trigger event

语句的这一部分标识在其上定义触发器的表。如果触发器事件为插入或删除操作,那么仅需要语句的类型和表名,如以下示例所示:

CREATE TRIGGER ins_qty
INSERT ON items -- an INSERT trigger event

定义触发操作

触发操作是当触发事件发生时执行的 SQL 语句。触发操作可以由 INSERT 、DELETE 、UPDATE 、EXECUTE FUNCTION 和 EXECUTE PROCEDURE 语句组成。但是,除了指定要执行什么操作外,还必须就触发语句何时执行这些操作。您有以下选择:

  • 在触发语句执行之前
  • 在触发语句执行之后
  • 针对触发语句作用的每一行

表上的单个触发器可以为上述每一时间定义操作。

要定义触发操作,指定该操作何时发生,然后提供要执行的 SQL 语句,可使用关键字 BEFORE 、AFTER 或 FOR EACH ROW 指定该操作何时发生。然后是括在圆括号中的触发操作。下面的触发操作定义指定在触发语句之前执行 SPL 例程 upd_items_p1 :

BEFORE(EXECUTE PROCEDURE upd_items_p1) -- a BEFORE action

完整的 CREATE TRIGGER 语句

要定义完整的 CREATE TRIGGER 语句,将触发器名称子句、触发事件子句和触发操作子句组合起来。下面的 CREATE TRIGGER 语句是将前例中的语句的各个组成部分组合在一起的结果。每次更新 items 表的 quantity 列时,此触发器将执行 SPL 例程 upd_items_p1。

CREATE TRIGGER upqty
UPDATE OF quantity ON items
BEFORE(EXECUTE PROCEDURE upd_items_p1);

如果在数据库服务器处理 CREATE TRIGGER 语句时,触发器定义中的数据库对象(例如,本例中的 SPL 例程 upd_items_p1 )不存在,那么返回错误。

使用触发操作

要有效地使用触发器,需要理解触发语句和生成的触发操作之间的关系。在指定发生触发操作的时间(即,BEFORE 、AFTER 或 FOR EACH ROW)时定义此关系。

BEFORE 和 AFTER 触发操作

在触发事件之前或之后发生的触发操作仅执行一次。BEFORE 触发操作在触发语句之前执行,即在触发器事件发生之前执行,AFTER 触发操作在触发语句操作完成之后执行。即使触发语句不处理任何行,BEFORE 和 AFTER 触发操作也会执行。

除其它用法外,还可以使用 BEFORE 和 AFTER 触发操作来确定触发语句的效果。例如,在更新 items 表的 quantity 列之前,可以调用 SPL 例程upd_items_p1 来计算表中所有项的订购总数,如下例所示,该过程将总数存储在名为 old_qty 的全局变量中。

CREATE PROCEDURE upd_items_p1()
DEFINE GLOBAL old_qty INT DEFAULT 0;
LET old_qty = (SELECT SUM(quantity) FROM items);
END PROCEDURE;

在完成触发更新之后,可以再次计算总数来看看更改了多少。下面的 SPL 例程 upd_items_p2 再次计算了 quantity 的总数并将结果存储在局部变量 new_qty 中。然后,它将 new_qty 与全局变量 old_qty 相比较,以查看所有订单的总量的增长是否超过 50%。若是,该过程将使用 RAISE EXCEPTION 语句来模拟 SQL 错误。

CREATE PROCEDURE upd_items_p2()
DEFINE GLOBAL old_qty INT DEFAULT 0;
DEFINE new_qty INT;
LET new_qty = (SELECT SUM(quantity) FROM items);
IF new_qty > old_qty * 1.50 THEN
RAISE EXCEPTION -746, 0, 'Not allowed - rule violation';
END IF
END PROCEDURE;

下列触发器调用 upd_items_p1 和 upd_items_p2 以防止对 items 表的 quantity 列点进行异常更新:

CREATE TRIGGER up_items
UPDATE OF quantity ON items
BEFORE(EXECUTE PROCEDURE upd_items_p1())
AFTER(EXECUTE PROCEDURE upd_items_p2());

如果更新使得对所有项的订购总量增长超过 50%,那么 upd_items_p2 中的 RAISE EXCEPTION 语句终止该触发器,并显示错误。当进行事务记录的数据库服务器中的触发器发生故障时,数据库服务器会回滚超过语句和触发操作进行的更改。有关触发器发生故障时所发生的情况的更多信息,请参阅《GBase 8s SQL 指南:语法》中的 CREATE TRIGGER 语句。

FOR EACH ROW 触发操作

FOR EACH ROW 触发操作对触发语句所作用的每一个行执行一次。例如,如果触发语句有下列语法,将对 manu_code列的值为 'KAR' 的 items 表中的每一行执行一次 FOR EACH ROW 触发操作:

UPDATE items SET quantity = quantity * 2
WHERE manu_code = 'KAR';

如果触发事件不处理任何行,将不会执行 FOR EACH ROW 触发操作。

对于表上的触发器,如果触发事件为 SELECT 语句,那么该触发器称为选择触发器,并且触发操作在完成时对检索到的行的所有处理之后再执行。但是,触发操作可能不会立即执行;原因是会对查询返回的行的每一个实例执行 FOR EACH ROW 操作。例如,在带有 ORDER BY 子句的 SELECT 语句中,必须先根据 WHERE 子句限定所有行,它们才能排序并返回。

REFERENCING 子句

当创建 FOR EACH ROW 触发操作时,通常必须在触发操作语句中指示您引用的是触发语句生效之前还是之后的列值。例如:假定您想要跟踪对items 表的 quantity 列的更新。为此,创建下表以记录该活动:

CREATE TABLE log_record
(item_num SMALLINT,
ord_num INTEGER,
username CHARACTER(8),
update_time DATETIME YEAR TO MINUTE,
old_qty SMALLINT,
new_qty SMALLINT);

要为此表中的 old_qty 和 new_qty 列提供值,必须能够引用 items 表中的 quantity 的旧值和新值。即,触发语句作用之前和之后的值。REFERENCING 子句可使您做的这一点。

REFERENCING 子句允许您创建可与列名组合起来的两个前缀,一个用于引用列的旧值,另一个用于引用列的新值。这些前缀称为相关名。可以根据您的要求创建一个或两个相关名。您指出使用关键字 OLD 和 NEW 创建的哪个相关名。下面的 REFERENCING 子句创建相关名 pre_upd 和post_upd 来引用行中的旧值和新值:

REFERENCING OLD AS pre_upd NEW AS post_upd

当更新 items 表中的某行中的 quantity 时,以下触发操作将在 log_record 中创建一行。INSERT 语句引用 item_num 和 order_num 列的旧值并引用 quantity 列的新值和旧值。

FOR EACH ROW(INSERT INTO log_record
VALUES (pre_upd.item_num, pre_upd.order_num, USER,
CURRENT, pre_upd.quantity, post_upd.quantity));

在 REFERENCING 子句中定义的相关名应用于触发语句作用的所有行。

重要

如果引用未被相关名限定的列名,数据库服务器不会专门在触发表的定义中搜索该列。必须总是将相关名与 FOR EACH ROW 触发操作中的 SQL 语句中的列名结合使用,除非该语句独立有效,而与触发操作无关。有关更多信息,请参阅《GBase 8s SQL 指南:语法》中的 CREATE TRIGGER 语句。

WHEN 条件

作为表上的触发器的选项,可在 WHEN 子句之前加上触发操作以使该操作依赖于测试结果。WHEN 子句由关键字 WHEN 以及跟随在其后的括在圆括号中的条件语句所组成。在 CREATE TRIGGER 语句中,WHEN 子句跟在关键字 BEFORE 、AFTER 或 OR EACH ROW 之后,触发操作列表之前。

当 WHEN 条件存在时,如果它求值为 true,那么按触发操作的出现顺序执行这些操作。如果 WHEN 条件求值为 false 或 unknown,那么不执行触发操作列表中的操作。如果触发器指定 FOR EACH ROW,那么还将针对每一行对条件进行求值。

在下面的触发器示例中,仅当 WHEN 子句中的条件为 true (即,如果更新后单价高于更新前单价的两倍)时才执行触发操作:

CREATE TRIGGER up_price
UPDATE OF unit_price ON stock
REFERENCING OLD AS pre NEW AS post
FOR EACH ROW WHEN(post.unit_price > pre.unit_price * 2)
(INSERT INTO warn_tab
VALUES(pre.stock_num, pre.manu_code, pre.unit_price,
post.unit_price, CURRENT));

有关 WHEN 条件的更多信息,请参阅《GBase 8s SQL 指南:语法》中的 CREATE TRIGGER 语句。

将 SPL 例程用作触发操作

触发器最强大的功能可能是能够将 SPL 例程作为触发操作进行调用,调用 SPL 例程的 EXECUTE PROCEDURE 或 EXECUTE FUNCTION 语句允许您将数据从触发表传递至 SPL 例程,还允许您使用由 SPL 例程返回的数据更新触发表。SPL 还允许您定义变量、对其指定数据、进行比较以及使用过程语句来完成触发操作内的复杂任务。

将数据传至 SPL 例程

可以在 EXECUTE PROCEDURE 或 EXECUTE FUNCTION 语句的参数列表中将数据传递至 SPL 例程。以下示例中的 EXECUTE PROCEDURE 语句将值从 items 表的 quantity 和 total_price 列传递至 SPL 例程 calc_totpr:

CREATE TRIGGER upd_totpr
UPDATE OF quantity ON items
REFERENCING OLD AS pre_upd NEW AS post_upd
FOR EACH ROW(EXECUTE PROCEDURE calc_totpr(pre_upd.quantity,
post_upd.quantity, pre_upd.total_price) INTO total_price);

将数据传递至 SPL 例程允许您在该例程执行的操作中使用数据值。

使用 SPL

在之前触发列中的 EXECUTE PROCEDURE 语句调用以下示例所示的 SPL 例程。在更新 items 表中的 quantity 时,该过程使用 SPL 计算需要对total_price 列作出的更改。该过程接收 quantity 的旧值和新值以及 total_price 的旧值。它用旧的总价除以旧的数值来得出单价。然后用新的数量乘以单价来得出新的总价。

CREATE PROCEDURE calc_totpr(old_qty SMALLINT, new_qty SMALLINT,
total MONEY(8)) RETURNING MONEY(8);
DEFINE u_price LIKE items.total_price;
DEFINE n_total LIKE items.total_price;
LET u_price = total / old_qty;
LET n_total = new_qty * u_price;
RETURN n_total;
END PROCEDURE;

在本示例中,SPL 允许触发器派生不能直接从触发表获得的数据。

用 SPL 例程中的数据更新非触发列

在触发操作内,EXECUTE PROCEDURE 语句的 INTO 子句允许您更新触发表中的非触发列。下例中的 EXECUTE PROCEDURE 语句调用包含 INTO 子句(该子句引用 total_price 列)的 calc_totpr SPL 过程:

FOR EACH ROW(EXECUTE PROCEDURE calc_totpr(pre_upd.quantity,
post_upd.quantity, pre_upd.total_price) INTO total_price);

更新到 total_price 中的值是 SPL 过程结束时由 RETURN 语句返回的。对触发语句作用的每一行更新 total_price 列。

触发器例程

可以定义称为触发器例程专用 SPL 例程,此类例程只能从触发器操作的 FOR EACH ROW 段进行调用。与 EXECUTE FUNCTION 或 EXECUTE PROCEDURE 例程可以从触发操作列表中调用的普通 UDR 不同,触发器例程包含自己的 REFERENCING 子句,可用于为触发操作修改的行中原有列和新列值定义相关名。这些相关名可以在触发器例程中的 SPL 语句中引用,为触发操作可在表或视图中修改数据的方式提供更大的灵活性。

触发器例程也可使用称为 DELETING 、INSERTING 、SELECTING 和 UPDATING 触发器类型的布尔运算符,以标识已调用触发器例程的触发器的类型。触发器例程还可以调用 mi_trigger* 例程(有时称为触发器自省类型)来获取关于已调用触发器例程的上下文的信息。

触发器例程由包含 WITH TRIGGER REFERENCES 关键字的 EXECUTE FUNCTION 或 EXECUTE PROCEDURE 语句调用。这些语句必须从触发操作的 FOR EACH ROW 段中调用触发器例程,而不是从 BEFORE 或 AFTER 段中进行调用。

有关支持定义和执行触发器例程的 SQL 的 CREATE FUNCTION 、CREATE PROCEDURE 、EXECUTE FUNCTION 和 EXECUTE PROCEDURE 语句的语法特征的信息,请参阅《GBase 8s SQL 指南:语法》。有关 mi_trigger* 例程的更多信息,请参阅《GBase 8s DataBlade API 程序员指南》。

表层次结构中的触发器

当您在超表上定义触发器时,表层次结构中的所有子表也会继承该触发器。因此,当您对层次结构中的表执行操作时,可对层次结构中作为对其定义触发器的表的子表的任何表执行触发器。

Select 触发器

当 CREATE TRIGGER 语句将指定表上的任何查询定义为其触发事件(

SELECT ON table

SELECT ON column-list ON table

)时,生成的触发对象是指定表的 Select 触发器。同一触发器还可以被包含将此表作为其基本表的触发列的视图上的查询激活。但是,SELECT 语句不能是视图上 INSTEAD OF 触发器的触发事件。

如果 CREATE TRIGGER 语句在嵌入 Select 触发事件的定义中也包含列列表,并且指定表上后续查询的投影列表不包含任何指定的列,那么该查询不能是此 Select 触发器的触发事件。

警告

Select 触发器不建议用于审计。不要出于执行应用程序指定审计的目的而在表或其列的子集上尝试创建 Select 触发器。一般情况下,通过创建 Select 触发器来跟踪表上的 Select 动作的数量,以在每次用户查询某个表时将审计记录插入到审计表中是不可能的。

例如,假设您在表 AuditedTable 上定义了 Select 触发器,且对 AuditedTable 持有 Select 特权的用户发出了以下查询:

SELECT a.* FROM (SELECT * FROM AuditedTable) AS a;

数据库服务器不发出错误,但是 AuditedTable 上的 SELECT 触发器不会被此查询激活。包含集合运算符(例如 UNION 或 INTERSECT)的查询,或者其它 Select 触发器不支持的语法,将会被基于 Select 触发器的审计记录策略无视。

因为执行 Select 触发器的大量的限制(部分在本章中列出),生成的 Select 触发操作通常仅对应于试图枚举的任何逻辑 Select 事件的子集(它可能为空)。

执行触发操作的 SELECT 语句

当创建 select 触发器时,仅某些类型的 SELECT 语句可以执行对该触发器定义的操作。Select 触发器仅当下列类型的 SELECT 语句执行:

  • 独立的 SELECT 语句t
  • SELECT 语句的选择列表中的集合子查询
  • 嵌入用户定义例程的 SELECT 语句
  • 视图

独立 SELECT 语句

假设您对表定义了下面的 Select 触发器:

CREATE TRIGGER hits_trig SELECT OF col_a ON tab_a
REFERENCING OLD AS hit
FOR EACH ROW (INSERT INTO hits_log
VALUES (hit.col_a, CURRENT, USER));

当触发列出现在独立 SELECT 语句的选择列表中时执行 Select 触发器。以下语句针对数据库服务器返回的行的每个实例执行 hits_trig 触发器上的触发操作:

SELECT col_a FROM tab_a;

查询投影列表中的集合子查询

当触发列出现在位于其它 SELECT 语句的投影列表中的集合子查询中时,将执行 Select 触发器。以下语句针对集合子查询返回的行的每个实例执行hits_trig 触发器上的触发操作:

SELECT MULTISET(SELECT col_a FROM tab_a) FROM ...

嵌入在用户定义例程中的 SELECT 语句

对嵌入在用户定义的例程(UDR)中的 SELECT 语句定义的选择触发器仅在以下情况下执行触发操作:

  • UDR 出现在 SELECT 语句的选择列表中
  • UDR 使用 EXECUTE PROCEDURE 语句调用

假设您创建包含语句 SELECT col_a FROM tab_a 的例程 new_proc。下面的每条语句针对嵌入的 SELECT 语句所返回行的每一个实例执行 hits_trig触发器的触发操作:

SELECT new_proc() FROM tab_b;
EXECUTE PROCEDURE new_proc;

视图

Select 触发器对其基础表包含触发列的引用的视图执行触发操作。但是,不能在视图上定义 Select 触发器。

假设您创建了下列视图:

CREATE VIEW view_tab AS
SELECT * FROM tab_a;

以下语句针对视图返回的行的每个实例执行 hits_trig 触发器上的触发操作:

SELECT * FROM view_tab;
SELECT col_a FROM tab_a;

执行 Select 触发器的限制

下列类型的 SELECT 语句不会触发 Select 触发器上的任何操作。

  • 触发列不在投影列表中(例如,出现在 SELECT 语句的 WHERE 子句中的列不会执行 Select 触发器)。
  • 引用远程表的 SELECT 语句。
  • SELECT 语句调用聚集函数或 OLAP 窗口聚集函数。
  • SELECT 语句包含集合运算符或(UNION 、UNION ALL 、INTERSECT 、MINUS 或 EXCEPT)。
  • SELECT 语句包含 DISTINCT 或 UNIQUE 关键字。
  • 包含 SELECT 语句的 UDR 表达式不在投影列表中。
  • SELECT 语句出现在 INSERT INTO 语句中。
  • SELECT 语句出现在滚动游标中。
  • 触发器是级联 Select 触发器。

级联 Select 触发器是其操作包含 SPL 例程的触发器,该例程本身具有触发 select 语句。但是,不执行级联 Select 触发器的操作,数据库服务器也不返回错误。

在表层次结构中的表的 Select 触发器

当您对超表定义 select 触发器,表层次结构中的所有子表也会继承此触发器。

有关覆盖和禁用继承触发器的信息,请参阅表层次结构中的触发器。

可重入触发器

可重入触发器指的是其中触发操作可引用触发表的情况。换句话说,也就是触发器事件和触发操作可作用于同一个表。例如,假设下面的 UPDATE 语句表示触发事件:

UPDATE tab1 SET (col_a, col_b) = (col_a + 1, col_b + 1);

以下触发操作是合法的,因为列 col_c 不是触发事件已更新的列:

UPDATE tab1 SET (col_c) = (col_c + 3);

在前面的示例中,对 col_a 或 col_b 的触发操作可能是非法的,因为触发操作不能是引用触发事件所更新的列的 UPDATE 语句。

重要

Select 触发器不能是可重入触发器。如果触发事件为 SELECT 语句,那么不能对同一个表执行触发操作。

有关描述在哪些情况下触发器可为或不可为可重入触发器的规则的列表,请参阅《GBase 8s SQL 指南:语法》中的 CREATE TRIGGER 语句。

视图上的 INSTEAD OF 触发器

视图是使用 CREATE VIEW 语句创建并使用 SELECT 语句定义的虚拟表。每个视图由若干行列集合组成,它们是在您每次通过查询引用该视图时,由其视图定义中的 SELECT 语句返回的。要在视图的基本表中插入、更新或删除行,可以定义 INSTEAD OF 触发器。

与表上的触发器不同,视图上的 INSTEAD OF 触发器导致 GBase 8s 忽略触发事件,而只执行触发操作。

有关 CREATE VIEW 语句和 INSTEAD OF 触发器语法和规则的信息,包括将对视图插入行的 INSTEAD OF 触发器的示例,请参阅《GBase 8s SQL 指南:语法》。

使用 INSTEAD OF 触发器对视图进行更新

在创建一个或多个表之后(如下例中名为 dept 和 emp 的表),然后又创建了基于 dept 和 emp 的视图(如名为 manager_info 的视图)之后,使用 INSTEAD OF 触发器更新该视图。

以下 CREATE TRIGGER 语句创建 manager_info_update,这是一个 INSTEAD OF 触发器,用来通过 manager_info 视图更新 dept 和 emp 表中的行。

CREATE TRIGGER manager_info_update
INSTEAD OF UPDATE ON manager_info
REFERENCING NEW AS n
FOR EACH ROW
(EXECUTE PROCEDURE updtab (n.empno, n.empname, n.deptno,));

CREATE PROCEDURE updtab (eno INT, ename CHAR(20), dno INT,)
DEFINE deptcode INT;
UPDATE dept SET manager_num = eno where deptno = dno;
SELECT deptno INTO deptcode FROM emp WHERE empno = eno;
IF dno !=deptcode THEN
UPDATE emp SET deptno = dno WHERE empno = eno;
END IF;
END PROCEDURE;

在创建了表、视图、触发器和 SPL 例程以后,数据库服务器将下面的 UPDATE 语句视作触发事件:

UPDATE manager_info
SET empno = 3666, empname = "Steve"
WHERE deptno = 01;

此触发 UPDATE 语句不会得到执行,但是此事件将造成执行触发器操作,即调用 updtab() SPL 例程。SPL 例程中的 UPDATE 语句将值更新到manager_info 视图的 emp 和 dept 基本表中。

跟踪触发操作

如果触发操作并未按您所期望的那样运行,那么将其放置在 SPL 例程中,并使用 SPL TRACE 语句来监视其操作。在启动跟踪之前,必须使用 SET DEBUG FILE TO 语句将输出定向到文件。

SQL 例程中的 TRACE 语句的示例

以下示例显示了添加到 SPL 例程 items_pct 中的 TRACE 语句。SET DEBUG FILE TO 语句将跟踪输出定向至路径名所指定的文件。TRACE ON 语句开始跟踪过程中的语句和变量。

CREATE PROCEDURE items_pct(mac CHAR(3))
DEFINE tp MONEY;
DEFINE mc_tot MONEY;
DEFINE pct DECIMAL;
SET DEBUG FILE TO 'pathname';

TRACE 'begin trace';
TRACE ON;
LET tp = (SELECT SUM(total_price) FROM items);
LET mc_tot = (SELECT SUM(total_price) FROM items
WHERE manu_code = mac);
LET pct = mc_tot / tp;
IF pct > .10 THEN
RAISE EXCEPTION -745;
END IF
TRACE OFF;
END PROCEDURE;

CREATE TRIGGER items_ins
INSERT ON items
REFERENCING NEW AS post_ins
FOR EACH ROW(EXECUTE PROCEDURE items_pct (post_ins.manu_code));

TRACE 输出的示例

以下示例显示了 items_pct 过程中的样本跟踪输出,这些输出出现在 SET DEBUG FILE TO 语句所指定的文件中。这些输出显示过程变量、过程参数、返回值和错误代码的值。

trace expression :begin trace
trace on
expression:
(select (sum total_price)
from items)
evaluates to $18280.77 ;
let tp = $18280.77
expression:
(select (sum total_price)
from items
where (= manu_code, mac))
evaluates to $3008.00 ;
let mc_tot = $3008.00
expression:(/ mc_tot, tp)
evaluates to 0.16
let pct = 0.16
expression:(> pct, 0.1)
evaluates to 1
expression:(- 745)
evaluates to -745
raise exception :-745, 0, ''
exception : looking for handler
SQL error = -745 ISAM error = 0 error string = = ''
exception : no appropriate handler

有关如何使用 TRACE 语句诊断 SPL 例程中的逻辑错误的更多信息,请参阅创建和使用 SPL 例程。

生成错误消息

当触发器因为 SQL 语句而失败时,数据库服务器将返回适用于特定失败原因的 SQL 错误号。

当触发操作是 SPL 例程时,可以使用两个保留错误号的其中之一针对错误情况生成错误消息。第一个是错误号 -745,它具有通用且固定的错误消息。第二个是错误号 -746,它允许您提供最多 70 字节的信息正文。

应用固定错误消息

可以将错误号 -745 应用于并非 SQL 错误的任何触发器故障。下列固定消息用于此错误:-745 Trigger execution has failed。

可以在 SPL 中将此消息应用于 RAISE EXCEPTION 语句。在以下示例中,如果 new_qty 大于 1.50 倍的 old_qty,那么生成错误号 -745 :

CREATE PROCEDURE upd_items_p2()
DEFINE GLOBAL old_qty INT DEFAULT 0;
DEFINE new_qty INT;
LET new_qty = (SELECT SUM(quantity) FROM items);
IF new_qty > old_qty * 1.50 THEN
RAISE EXCEPTION -745;
END IF
END PROCEDURE

如果您正在使用 DB-Access,那么错误 -745 消息的文本在屏幕的底部显示,如下图所示。

图: 带有固定消息的错误消息 -745

Press CTRL-W for Help
SQL: New Run Modify Use-editor Output Choose Save Info Drop Exit
Modify the current SQL statements using the SQL editor.

--------------------- stores8@myserver --------- Press CTRL-W for Help ----

INSERT INTO items VALUES( 2, 1001, 2, 'HRO', 1, 126.00);

745: Trigger execution has failed.

如果触发器在 SQL API 中通过 SQL 语句调用包含错误的过程,数据库服务器将把 SQL 错误变量设置为 -745,并将其返回至程序。要显示消息正文,遵循 GBase 8s 应用程序开发工具提供的过程以检索 SQL 错误消息的正文。

生成可变错误消息

错误号 -746 允许您提供错误消息的正文。就像前面的示例,如果 new_qty 大于 1.50 倍的 old_qty,以下示例也将生成错误。但是,在本例中,错误号为 -746,并且消息正文 Too many items for Mfr. 的项过多是作为 RAISE EXCEPTION 语句中的第三个参数提供的。有关此语句的语法和使用的更多信息,请参阅创建和使用 SPL 例程 中的 RAISE EXCEPTION 语句。

CREATE PROCEDURE upd_items_p2()
DEFINE GLOBAL old_qty INT DEFAULT 0;
DEFINE new_qty INT;
LET new_qty = (SELECT SUM(quantity) FROM items);
IF new_qty > old_qty * 1.50 THEN
RAISE EXCEPTION -746, 0, 'Too many items for Mfr.';
END IF
END PROCEDURE;

如果使用 DB-Access 提交触发语句,并且如果 new_qty 大于 old_qty,那么您将得到下图显示的结果。

图: 带有用户指定消息正文的错误号 -746

Press CTRL-W for Help
SQL: New Run Modify Use-editor Output Choose Save Info Drop Exit
Modify the current SQL statements using the SQL editor.

-------------------- store7@myserver --------- Press CTRL-W for Help -----

INSERT INTO items VALUES( 2, 1001, 2, 'HRO', 1, 126.00);

746: Too many items for Mfr.

如果在 SQL API 中通过 SQL 语句调用触发器,那么数据库服务器将 sqlcode 设置为 -746,并在 SQL 通信区域(SQL;CA)的sqlerrm 字段中返回消息正文。有关如何使用 SQL;CA 的更多信息,请参阅您的 SQL API 出版物。

总结

为介绍触发器,本章讨论了下列主题:

  • CREATE TRIGGER 语句的各个组成部分
  • 可作为触发事件的 DML 语句的类型
  • 可作为触发操作的 SQL 语句的类型
  • 如何创建 BEFORE 和 AFTER 触发操作以及如何使用它们来确定触发语句的影响
  • 如何创建 FOR EACH ROW 触发操作,以及如何使用 REFERENCING 子句引用触发语句执行之前和之后的列值
  • 视图上的 INSTEAD OF 触发器,其触发事件将被忽略,但触发操作可以修改视图的基本表
  • 将 SPL 例程用作触发操作的好处
  • 将触发例程作为触发操作调用的特殊功能
  • 在触发操作的执行异常时如何跟踪它们
  • 如何在触发操作内生成两种类型的错误消息